﻿using System;
using System.IO.Ports;
using System.Threading;
using System.Collections;
using System.Text;

using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;
using GT = Gadgeteer;

using Microsoft.SPOT;
using Gadgeteer.Modules.Community.XBee;

namespace Gadgeteer.Modules.Community
{
    /// <summary>
    /// An XBee client for Gadgeteer or PC
    /// </summary>
    public class XBeeClient : GTM.Module
    {
        private int numRetries = 0;
        private int frameId = 0;
        private GTI.Serial serialLine;
        private delegate void ProcessSerialMessageAndCallback(byte [] message);
        private Queue multipartMessagesQueue = new Queue();
        private Hashtable incomingMultipartMessagePacketsReceived = new Hashtable();
        private Hashtable incomingMultipartMessageParts = new Hashtable();
        private TransmitRequest currentTransmitRequestInFlight = null;

        /// <summary>
        /// if you enable this feature by setting RecordBytesReceivedHistory to true, this will hold all the bytes received
        /// </summary>
        public ArrayList BytesReceivedHistory = new ArrayList();

        internal XBeeClient(GTI.Serial serialLine)
        {
            this.serialLine = serialLine;
            this.serialLine.DataReceived += new GTI.Serial.DataReceivedEventHandler(serialLine_DataReceived);
        }

        /// <summary>
        /// Creates an XBeeClient
        /// </summary>
        /// <param name="socketNumber">For Gadgeteer, this is your socket number, but for PC, this is the number of your COM Port</param>
        public XBeeClient(int socketNumber)
        {
            GT.Socket socket = GT.Socket.GetSocket(socketNumber, true, this, null);

            socket.EnsureTypeIsSupported(new char[] { 'K', 'U' }, this);

            this.serialLine = new GTI.Serial(socket, 9600, GTI.Serial.SerialParity.None, GTI.Serial.SerialStopBits.One, 8, GTI.Serial.HardwareFlowControl.NotRequired, this);
            this.serialLine.Open();

            this.serialLine.DataReceived += new GTI.Serial.DataReceivedEventHandler(serialLine_DataReceived);
        }

        #region API - Public Events
        /// <summary>
        /// setting this will enable recording every byte that is received for debugging
        /// </summary>
        public bool RecordBytesReceivedHistory { get; set; }

        /// <summary>Occurs when an asynchronous discover nodes operation completes.</summary>
        public event DiscoverNodesCompletedEventHandler DiscoverNodesCompleted;

        /// <summary>Occurs when an asynchronous upload string operation completes.</summary>
        public event UploadStringCompletedEventHandler UploadStringCompleted;

        /// <summary>Occurs when you send a message and receive the status.</summary>
        public event TransmitStatusReceivedEventHandler TransmitStatusReceived;

        /// <summary>Occurs when a response from an AT command is received.</summary>
        public event ATResponseReceivedEventHandler ATResponseReceived;

        /// <summary>Occurs when a transmit receive message is received (we're receiving data).</summary>
        public event TransmitReceiveEventHandler TransmitReceive;

        /// <summary>Occurs when data is received.</summary>
        public event DataReceivedEventHandler DataReceived;

        /// <summary>Occurs when picture is received.</summary>
        public event PictureReceivedEventHandler PictureReceived;

        /// <summary>Occurs when a string is received.</summary>
        public event StringReceivedEventHandler StringReceived;

        /// <summary>Occurs when a test completes, this will get fired for each test in your test run.</summary>
        public event TestCompletedEventHandler TestCompleted;

        #endregion

        #region API - Public Methods

        /// <summary>
        /// Runs a functional test suite for diagnostics
        /// </summary>
        /// <param name="numExpectedPeers">number of XBee peers</param>
        /// <returns>the test list that you can use to print/inspect</returns>
        public BaseTest[] RunFunctionalTests(int numExpectedPeers)
        {
            Debug.Print("\nRunning Functional Tests**************************************************");

            BaseTest[] tests = new BaseTest[] { new SH_Test(), new NodeDiscoveryTest(), new ShortBroadcastStringTest(), new ShortStringTest(), new SmallUploadDataAsyncTest(), new MediumBroadcastStringTest(), new MediumStringTest(), new LargeBroadcastStringTest(), new LargeStringTest() };

            TestContext context = new TestContext();
            context.Properties["NumExpectedPeers"] = numExpectedPeers;
            context.XBee = this;
            context.Peers = new NodeDescription[0];

            for (int i = 0; i < tests.Length; i++)
            {
                tests[i].Arrange(context);
                tests[i].Act(context);

                System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();

                do
                {
                    try
                    {
                        tests[i].Assert(context);
                    }
                    catch (XBeeAssertFailed assertException)
                    {
                        tests[i].Error = assertException;
                        if (!tests[i].HaltTestRunOnFailure)
                        {
                            Thread.Sleep(1000);
                            Microsoft.SPOT.Debug.Print("Elapsed milliseconds: " + stopwatch.ElapsedMilliseconds);
                            continue;
                        }
                        else
                        {
                            return tests;
                        }
                    }

                    tests[i].Error = null;
                    tests[i].Passed = true;
                    break;

                } while (stopwatch.ElapsedMilliseconds < tests[i].TimeoutMilliseconds);

                tests[i].ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;

                TestCompletedEventArgs args = new TestCompletedEventArgs(tests[i], null, false, null);
                OnTestCompleted(args);
            }

            Debug.Print("\nFinished Running Functional Tests**************************************************\n\n");
            return tests;
        }

        /// <summary>
        /// if RecordBytesReceivedHistory is set to true, all bytes received will be recorded, 
        /// this method will print them out
        /// </summary>
        public void PrintBytesReceivedHistory()
        {
            Debug.Print("Printing Bytes Received History");
            
            byte[] bytes = new byte[BytesReceivedHistory.Count];
            BytesReceivedHistory.CopyTo(bytes);

            Debug.Print(HexUtil.ToHexString(bytes));
        }

        /// <summary>
        /// Broadcast data to all nodes
        /// </summary>
        /// <param name="data">data to send to all nodes</param>
        public void UploadDataAsync(byte[] data)
        {
            int requestFrameId = GetNextAvailableFrameId();

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartBroadcastMessage(data, MessageDataTypeEnum.Data, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// send data to a specific XBee
        /// </summary>
        /// <param name="nodeDescription">the node to send the data to</param>
        /// <param name="data">data to send to a specific XBee</param>
        public void UploadDataAsync(NodeDescription nodeDescription, byte[] data)
        {
            int requestFrameId = GetNextAvailableFrameId();

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartMessage(nodeDescription, data, MessageDataTypeEnum.Data, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// Broadcast picture to all nodes
        /// </summary>
        /// <param name="data">picture to send to all nodes</param>
        public void UploadPictureAsync(byte[] data)
        {
            int requestFrameId = GetNextAvailableFrameId();

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartBroadcastMessage(data, MessageDataTypeEnum.Picture, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// send picture to a specific XBee
        /// </summary>
        /// <param name="nodeDescription">the node to send the picture to</param>
        /// <param name="data">picture to send to a specific XBee</param>
        public void UploadPictureAsync(NodeDescription nodeDescription, byte[] data)
        {
            int requestFrameId = GetNextAvailableFrameId();

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartMessage(nodeDescription, data, MessageDataTypeEnum.Picture, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// Broadcast string to all nodes
        /// </summary>
        /// <param name="message">string to send to all nodes</param>
        public void UploadStringAsync(string message)
        {
            int requestFrameId = GetNextAvailableFrameId();

            byte[] data = UTF8Encoding.UTF8.GetBytes(message);

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartBroadcastMessage(data, MessageDataTypeEnum.String, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// Asyncronously upload a string to a remote XBee
        /// </summary>
        /// <param name="nodeDescription">Describes the remote XBee, usually created through <see cref="T:Gadgeteer.Modules.Community.XBeeClient.DiscoverNodesAsync" /></param>
        /// <param name="message"></param>
        public void UploadStringAsync(NodeDescription nodeDescription, string message)
        {
            int requestFrameId = GetNextAvailableFrameId();

            byte[] data = UTF8Encoding.UTF8.GetBytes(message);

            TransmitRequest[] transmitRequests = MultipartMessageBuilder.BuildMultipartMessage(nodeDescription, data, MessageDataTypeEnum.String, (ushort)requestFrameId);

            foreach (TransmitRequest tr in transmitRequests)
            {
                multipartMessagesQueue.Enqueue(tr);
            }

            ProcessOutboundMessageQueue();
        }

        /// <summary>
        /// Asyncronously call AT command 'ND' to discover XBees
        /// </summary>
        public void DiscoverNodesAsync()
        {
            int requestFrameId = GetNextAvailableFrameId();

            byte[] message = APIMessageBuilder.BuildATCommand("ND", (ushort)requestFrameId);

            EscapeAndSendPacket(message);
        }

        /// <summary>
        /// Asyncronously issue a local AT command
        /// </summary>
        /// <param name="command">the 2 char command</param>
        public void SendLocalATCommandAsync(string command)
        {
            int requestFrameId = GetNextAvailableFrameId();

            byte[] message = APIMessageBuilder.BuildATCommand(command, (ushort)requestFrameId);

            EscapeAndSendPacket(message);
        }

        #endregion

        #region Protected Methods For Invoking Event Handlers

        /// <summary>
        /// called when a Transmit Receive message that is a data type is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.DataReceivedEventArgs" /> that contains event data.</param>
        protected virtual void OnDataReceived(DataReceivedEventArgs e)
        {
            if (this.DataReceived != null)
            {
                this.DataReceived(this, e);
            }
        }

        /// <summary>
        /// called when a Transmit Receive message that is a picture type is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.PicutreReceivedEventArgs" /> that contains event data.</param>
        protected virtual void OnPictureReceived(PictureReceivedEventArgs e)
        {
            if (this.PictureReceived != null)
            {
                this.PictureReceived(this, e);
            }
        }

        /// <summary>
        /// called when a Transmit Receive message that is a string type is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.StringReceivedEventArgs" /> that contains event data.</param>
        protected virtual void OnStringReceived(StringReceivedEventArgs e)
        {
            if (this.StringReceived != null)
            {
                this.StringReceived(this, e);
            }
        }

        /// <summary>
        /// called when a Transmit Receive message is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.TransmitReceiveEventArgs" /> that contains event data.</param>
        protected virtual void OnTransmitReceive(TransmitReceiveEventArgs e)
        {
            if (this.TransmitReceive != null)
            {
                this.TransmitReceive(this, e);
            }
        }

        /// <summary>
        /// called when AT Response is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.ATResponseReceivedEventArgs" /> that contains event data.</param>
        protected virtual void OnATResponseReceived(ATResponseReceivedEventArgs e)
        {
            if (this.ATResponseReceived != null)
            {
                this.ATResponseReceived(this, e);
            }

            if (e.Result is NodeDiscoveryResponse && this.DiscoverNodesCompleted != null)
            {
                DiscoverNodesCompletedEventArgs nodeCompletedArgs = new DiscoverNodesCompletedEventArgs((NodeDiscoveryResponse)e.Result, null, false, null);
                this.DiscoverNodesCompleted(this, nodeCompletedArgs);
            }
        }

        /// <summary>
        /// called when a Transmit Status message is received
        /// </summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.TransmitStatusReceivedEventArgs" /> that contains event data.</param>
        protected virtual void OnTransmitStatusReceived(TransmitStatusReceivedEventArgs e)
        {
            if (e.Result.DeliveryStatus == 0x00)
            {
                numRetries = 0;
                currentTransmitRequestInFlight = null;
                ProcessOutboundMessageQueue();
            }
            else
            {
                Debug.Print("Error - DeliveryStatus not 0x00, was " + HexUtil.ToHexString((byte)e.Result.DeliveryStatus));
                if (numRetries < 3)
                {
                    EscapeAndSendPacket(currentTransmitRequestInFlight.Message);
                }
                numRetries++;
            }

            if (this.TransmitStatusReceived != null)
            {
                this.TransmitStatusReceived(this, e);
            }
        }

        /// <summary>Raises the <see cref="E:Gadgeteer.Modules.Community.XBeeClient.DiscoverNodesCompleted" /> event.</summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.DiscoverNodesCompletedEventArgs" /> object containing event data.</param>
        protected virtual void OnDiscoverNodesCompleted(DiscoverNodesCompletedEventArgs e)
        {
            if (this.DiscoverNodesCompleted != null)
            {
                this.DiscoverNodesCompleted(this, e);
            }
        }

        /// <summary>Raises the <see cref="E:Gadgeteer.Modules.Community.XbeeClient.DiscoverNodesCompleted" /> event.</summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.DiscoverNodesCompletedEventArgs" /> object containing event data.</param>
        protected virtual void OnUploadStringCompleted(UploadStringCompletedEventArgs e)
        {
            if (this.UploadStringCompleted != null)
            {
                this.UploadStringCompleted(this, e);
            }
        }

        /// <summary>Raises the <see cref="E:Gadgeteer.Modules.Community.XbeeClient.TestCompleted" /> event.</summary>
        /// <param name="e">A <see cref="T:Gadgeteer.Modules.Community.XBee.TestCompletedEventArgs" /> object containing event data.</param>
        protected virtual void OnTestCompleted(TestCompletedEventArgs e)
        {
            if (this.TestCompleted != null)
            {
                this.TestCompleted(this, e);
            }
        }

        #endregion

        #region Private Methods
        private void ProcessOutboundMessageQueue()
        {
            if (multipartMessagesQueue.Count > 0)
            {
                currentTransmitRequestInFlight = (TransmitRequest)multipartMessagesQueue.Dequeue();
                EscapeAndSendPacket(currentTransmitRequestInFlight.Message);
            }
        }

        private void ProcessTransmitReceive(TransmitReceive transmitReceive)
        {
            TransmitReceiveEventArgs args = new TransmitReceiveEventArgs(transmitReceive, null, false, null);

            OnTransmitReceive(args);

            if (transmitReceive.MultipartMessage)
            {
                if (!incomingMultipartMessagePacketsReceived.Contains(transmitReceive.SourceAddress64Bit))
                {
                    Debug.Print("Incoming packets from " + transmitReceive.SourceAddress64Bit);
                    // TODO: what if they send a multipart packet that only contains 1? i.e. error
                    incomingMultipartMessagePacketsReceived.Add(transmitReceive.SourceAddress64Bit, 1);
                    ArrayList packetList = new ArrayList();
                    packetList.Add(transmitReceive);
                    incomingMultipartMessageParts.Add(transmitReceive.SourceAddress64Bit, packetList);
                }
                else
                {
                    incomingMultipartMessagePacketsReceived[transmitReceive.SourceAddress64Bit] = (int)incomingMultipartMessagePacketsReceived[transmitReceive.SourceAddress64Bit] + 1;
                    ((ArrayList)incomingMultipartMessageParts[transmitReceive.SourceAddress64Bit]).Add(transmitReceive);
                }

                if ((int)incomingMultipartMessagePacketsReceived[transmitReceive.SourceAddress64Bit] == transmitReceive.TotalPacketCount)
                {
                    Debug.Print("Received all multipart packets, processing");

                    ArrayList packets = (ArrayList)incomingMultipartMessageParts[transmitReceive.SourceAddress64Bit];

                    int totalMessageSize = 0;

                    MessageDataTypeEnum dataTypeEnum = MessageDataTypeEnum.Undefined;
                    foreach (TransmitReceive tr in packets)
                    {
                        if (dataTypeEnum == MessageDataTypeEnum.Undefined)
                        {
                            dataTypeEnum = tr.MessageDataType;
                        }
                        else if (dataTypeEnum != tr.MessageDataType)
                        {
                            throw new XBeeClientException("A multipart packet with a data type of " + tr.MessageDataType.ToString() + " was received but was not consistent with the previous packet of type " + dataTypeEnum.ToString());
                        }

                        totalMessageSize += tr.ReceivedData.Length;
                    }

                    byte[] data = new byte[totalMessageSize];
                    int currDataIndex = 0;

                    foreach (TransmitReceive tr in packets)
                    {
                        Array.Copy(tr.ReceivedData, 0, data, currDataIndex, tr.ReceivedData.Length);
                        currDataIndex += tr.ReceivedData.Length - 1;
                    }

                    incomingMultipartMessageParts.Remove(transmitReceive.SourceAddress64Bit);
                    incomingMultipartMessagePacketsReceived.Remove(transmitReceive.SourceAddress64Bit);

                    switch (dataTypeEnum)
                    {
                        case MessageDataTypeEnum.Picture:
                            PictureReceivedEventArgs pictureReceivedArgs = new PictureReceivedEventArgs(data, null, false, null);
                            OnPictureReceived(pictureReceivedArgs);
                            break;
                        case MessageDataTypeEnum.Data:
                            DataReceivedEventArgs dataReceivedArgs = new DataReceivedEventArgs(data, null, false, null);
                            OnDataReceived(dataReceivedArgs);
                            break;
                        case MessageDataTypeEnum.String:
                            char[] chars = System.Text.UTF8Encoding.UTF8.GetChars(data);
                            string message = new string(chars);
                            StringReceivedEventArgs stringReceivedArgs = new StringReceivedEventArgs(message, null, false, null);
                            OnStringReceived(stringReceivedArgs);
                            break;
                    }
                }
            }
        }

        private void ProcessATResponse(ATResponse atResponse)
        {
            ATResponseReceivedEventArgs args = new ATResponseReceivedEventArgs(atResponse, null, false, null);
            OnATResponseReceived(args);
        }

        private void ProcessTransmitStatus(TransmitStatus transmitStatus)
        {
            TransmitStatusReceivedEventArgs args = new TransmitStatusReceivedEventArgs(transmitStatus, null, false, null);
            OnTransmitStatusReceived(args);
        }

        private int GetNextAvailableFrameId()
        {
            // if the frame Id is 255 reset to 0. FrameId is a ushort
            Interlocked.CompareExchange(ref frameId, 0, 255);

            // Thread safe
            return Interlocked.Increment(ref frameId);
        }

        void serialLine_DataReceived(GTI.Serial sender, SerialData data)
        {
            Debug.Print("serialLine_DataReceived");
            Queue queue = new Queue();
            byte[] message = null;

            int numBytesRead = 0;
            int messageLength = 0;
            int messageSum = 0;
            ushort correlationId = 0;
            ushort frameType = 0;

            bool isEscaped = false;
            ushort numEscaped = 0;

            while (serialLine.BytesToRead > 0 || queue.Count > 0)
            {
                // if we have bytes in the queue (i.e. message has started to arrive) but don't have them from serial line yet, need to wait
                if (serialLine.BytesToRead == 0)
                {
                    System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();

                    while (serialLine.BytesToRead == 0)
                    {
                        Debug.Print("Waiting for more bytes from serial port, message length indicates more bytes comming");
                        Thread.Sleep(100);

                        if (stopwatch.ElapsedMilliseconds > 5000)
                        {
                            throw new ApplicationException("Timeout waiting for more bytes. Timeout is set to 5 seconds");
                        }
                    }
                }

                byte b = (byte)serialLine.ReadByte();
                numBytesRead++;
                Debug.Print("Read byte: " + HexUtil.ToHexString(b));

                if (RecordBytesReceivedHistory)
                {
                    BytesReceivedHistory.Add(b);
                }

                // i.e. 
                if (isEscaped)
                {
                    b = (byte)(0x20 ^ b);
                    Debug.Print("Unescaped byte, new value: " + HexUtil.ToHexString(b));
                } 
                else if ((ushort)b == (ushort)APIMessageEnum.EscapedByte)
                {
                    Debug.Print("Escape byte detected");
                    isEscaped = true;
                    numEscaped++;
                    continue;
                }

                queue.Enqueue(b);

                if (!isEscaped && (ushort)b == (ushort)APIMessageEnum.StartDelimiter)
                {
                    Debug.Print("Start Delimiter detected");
                    if (numBytesRead != 1)
                    {
                        Debug.Print("ERROR: a new message has started processing but an old message hasn't finished processing");
                        numBytesRead = 1;
                    }

                    messageSum = 0;
                    continue;
                }

                // TODO: there are certainly bugs around setting the escaped state and unsetting
                isEscaped = false;

                int frameIndex = numBytesRead - 1 - numEscaped;
                switch (frameIndex)
                {
                    case 0:
                        Debug.Print("Warning! The first byte read was not the StartDelimiter");
                        PrintBytesReceivedHistory();
                        continue;
                    case 2:
                        Debug.Print("Message length: " + HexUtil.ToHexString(b));
                        messageLength = b;
                        break;
                    case 3:
                        Debug.Print("FrameType: " + HexUtil.ToHexString(b));
                        frameType = b;
                        break;
                    case 4:
                        if(frameType != (ushort)APIMessageEnum.TransmitReceive)
                        {
                            Debug.Print("Correlation Id: " + HexUtil.ToHexString(b));
                            correlationId = b;
                        }
                        break;
                }
                
                // checksum does not include the start delimiter and the 2 length bytes
                if (frameIndex > 2)
                {
                    messageSum += b;
                }

                // 4 = start delimiter, 2 bytes for message length, and check sum
                // add any escaped as they aren't included with checksum
                bool endOfFrame = numBytesRead == 4 + messageLength + numEscaped;

                if (endOfFrame)
                {
                    Debug.Print("End of message detected by using messageLength + numEscaped + 4");
                    if ((messageSum & 0xFF) == 0xFF)
                    {
                        Debug.Print("Checksum valid");
                    }
                    else
                    {
                        Debug.Print("INVALID CHECKSUM!!!");
                    }

                    message = new byte[queue.Count];

                    int i = 0;
                    while (queue.Count > 0)
                    {
                        message[i++] = (byte)queue.Dequeue();
                    }

                    ProcessFrame(message, frameType);

                    Debug.Print("setting numBytesRead to 0 after process frame");
                    numBytesRead = 0;
                    numEscaped = 0;
                    continue;
                }

            }

            Debug.Print("exiting serialLine_DataReceived");
        }

        private void ProcessFrame(byte[] message, ushort frameType)
        {
            if (frameType == (ushort)APIMessageEnum.ATResponse)
            {
                ATResponse atResponse = APIMessageFactory.CreateATResponse(message);
                ProcessATResponse(atResponse);
            }
            else if (frameType == (ushort)APIMessageEnum.TransmitStatus)
            {
                TransmitStatus transmitStatus = APIMessageFactory.CreateTransmitStatus(message);
                ProcessTransmitStatus(transmitStatus);
            }
            else if (frameType == (ushort)APIMessageEnum.TransmitReceive)
            {
                TransmitReceive transmitReceive = APIMessageFactory.CreateTransmitReceive(message);
                ProcessTransmitReceive(transmitReceive);
            }
        }

        private void EscapeAndSendPacket(byte[] packet)
        {
            packet = Escape(packet);
            this.serialLine.Write(packet, 0, packet.Length);
        }

        private byte[] Escape(byte[] message)
        {
            int numNeedingEscaping = 0;

            for (int i = 1; i < message.Length; i++)
            {
                // TODO: escape the XON and XOFF
                if (NeedsEscaping(message[i]))
                {
                    numNeedingEscaping++;
                }
            }

            if(numNeedingEscaping == 0)
            {
                return message;
            }

            byte[] escapedMessage = new byte[message.Length + numNeedingEscaping];
            
            escapedMessage[0] = message[0];

            for(int i = 1, j = 1; i < message.Length && j < escapedMessage.Length; i++, j++)
            {
                if (NeedsEscaping(message[i]))
                {
                    escapedMessage[j] = (byte)APIMessageEnum.EscapedByte;
                    j++;
                    escapedMessage[j] = (byte)((ushort)message[i] ^ 0x20);
                }
                else
                {
                    escapedMessage[j] = message[i];
                }
            }

            return escapedMessage;
        }

        private bool NeedsEscaping(byte b)
        {
            return b == (byte)APIMessageEnum.StartDelimiter || b == (byte)APIMessageEnum.EscapedByte;
        }

        #endregion
    }
}
